home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / libs / anivga12 / jumpnrun / music.pas < prev    next >
Pascal/Delphi Source File  |  1992-09-24  |  21KB  |  527 lines

  1. unit music;                                                      { version 1.0 }
  2.  
  3.  
  4. (******************************************************** 1990 J.C. Kessels ****
  5. Play music in the background.
  6.  
  7.  
  8. This unit gives you music capabilities with a BASIC syntax. The music
  9. will be played in the background, so your program can continue with
  10. other things. The music can also be played in the foreground.
  11.  
  12. This unit is very easy to use. There are only three procedures and one
  13. function interfaced outwards. All the rest is automatic (installing,
  14. uninstalling, interpreting the music, etc.)!
  15.  
  16.  
  17. PlayMusic(string);
  18.           Start playing a string of music in the background. The string
  19.           is a normal character string containing music 'commands' as
  20.           described below. If there is already music playing, then it is
  21.           first shut off. All settings are reset to their default.
  22.  
  23. PlayMusicForeground(string);
  24.           Start playing a string of music, and wait for it to finish. If
  25.           there is already music playing, then it is first shut off. All
  26.           settings are reset to their default.
  27.           This procedure simply calls the "PlayMusic" procedure, and then
  28.           loops until MusicBusy (described later) is true.
  29.  
  30. MusicOff;
  31.           Turn music off.
  32.  
  33. if MusicBusy then ...
  34.           Return TRUE if there is currently music playing.
  35.  
  36. See at the end of this unit for a small demonstration program.
  37.  
  38.  
  39.  
  40.  
  41.  
  42. The music-commands syntax is (BASIC compatible):
  43.  
  44. [>,<]A..G[#,+,-](n)[.]
  45.           Play note A..G in the current octave. There are 12 notes per
  46.           octave: C, C#, D, D#, E, F, F#, G, G#, A, A#, B.
  47.            If the note is prefixed by '>', then it is transposed one octave
  48.             upwards.
  49.            If the note is prefixed by '<', then it is transposed one octave
  50.             downward.
  51.            if the note is followed by '#' or '+', then the note is made
  52.             "sharp" (one note up, 'D' becomes 'D#', 'E' becomes 'F').
  53.            If the note is followed by '-', then the note is made "flat" (one
  54.             note down, 'D' becomes 'C#').
  55.            If the note is followed by a number, then the number specifies the
  56.             length of this note, overriding the default notelength set by 'L'.
  57.            Every period following the notenumber will increase the playtime
  58.             by 3/2.
  59.           Example:   >B+3.
  60.                      >      : transposed
  61.                       B     : note B
  62.                        +    : sharp
  63.                         3   : length 3
  64.                          .  : 3/2 longer
  65. N(n)[.]   Play note "n", in which "n" is a number 0..84. There are 7 octaves,
  66.           12 notes per octave. Note 0 means: silence. The first note in the
  67.           first octave is 'N1'.
  68.            Every period following the notenumber will increase the playtime
  69.             by 3/2.
  70. O(n)      Sets the octave to "n", in which "n" is a number 0..7. Each octave
  71.           goes from note 'C' to 'B'. Octave 3 starts with middle 'C'. Default
  72.           octave is 4.
  73. L(n)      Set the default length of following notes to "n", in which "n" is
  74.           a number 1..64. L1 = whole notes, L2 = half notes, L4 = quarter
  75.           notes, etc. Default length is 4. In one minute fit 120 quarter
  76.           notes ('L4'), adjustable with the 'T' (tempo) command.
  77. T(n)      Set the tempo to "n", in which "n" is a number 32..255. The tempo
  78.           is the number of quarter notes ('L4') that are played per minute.
  79.           The higher the tempo, the faster the music. Default tempo is 120.
  80. MN        Music Normal. Every note plays seven-eights of the time set by
  81.           'L', and is followed by a pause of one-eight. Thus, every note is
  82.           followed by a small silence, making the music more natural.
  83. ML        Music Legato. Every note plays the full time set by 'L'. Thus, every
  84.           note is immediately followed by the next note, making the music a
  85.           bit synthetic.
  86. MS        Music Staccato. Every note plays three-quarters of the time
  87.           set by 'L', and is followed by a pause of one-quarter. Thus, every
  88.           note is followed by a clearly audible silence, making the music
  89.           very rithmic.
  90. P(n)[.]   Insert a pause with a length of "n", in which "n" is a number
  91.           1..64.
  92.            Every period following the number will increase the playtime
  93.             by 3/2.
  94.  
  95. Not supported (ignored):
  96. MF        Foreground: Cannot switch between foreground/background.
  97. MB        Background: Cannot switch between foreground/background.
  98. Xs$;      Include string: Cannot include substrings.
  99. =n;       Use variable "n": Cannot replace variable's names by their contents.
  100.  
  101. Spaces are allowed between commands, but not inside commands.
  102. Upper/lowercase is not important.
  103.  
  104.  
  105.  
  106.  
  107. THEORY.
  108.  
  109. This unit installs itself in the timertick interrupt $1C (procedure
  110. "MusicNext"). With every timertick a buffer is checked. If there is any
  111. music to be played in the buffer, then a single note from the buffer is
  112. played.
  113.  
  114.  
  115.  
  116.  
  117. This unit was inspired by a (buggy and incomplete) public domain unit
  118. written by Michael Quinlan, 9/17/85.
  119.  
  120.  
  121.  
  122. J.C. Kessels
  123. Philips de Goedelaan 7
  124. 5615 PN Eindhoven
  125. Netherlands
  126. *******************************************************************************)
  127.  
  128.  
  129.  
  130.  
  131. Interface
  132. procedure MusicOff;
  133. procedure PlayMusic(s : string);
  134. procedure PlayMusicForeground(s : string);
  135. function MusicBusy : boolean;
  136.  
  137.  
  138.  
  139.  
  140. Implementation
  141. uses dos;
  142.  
  143.  
  144.  
  145.  
  146.  
  147. var
  148.   OldInt1C        : pointer;               { Pointer to old interrupt routine. }
  149.   ExitSave        : pointer;             { Pointer to previous exit procedure. }
  150.   MusicString     : string;                         { The string to be played. }
  151.   MusicHere       : word;  { Pointer into MusicString, non-zero while playing. }
  152.   MusicDelay1     : word;             { Clockticks countdown for current note. }
  153.   MusicDelay2     : word;             { Clockticks countdown for current note. }
  154.   MusicNoteLength : word;                               { Current note length. }
  155.   MusicTempo      : word;                                     { Current tempo. }
  156.   MusicOctave     : word;                                    { Current octave. }
  157.   MusicKind       : word;              { 8 = Legato, 7 = Normal, 6 = Staccato. }
  158.   { Array with coded frequencies: 12 notes per octave (C, C#, D, D#, E, F, F#,
  159.     G, G#, A, A#, B), 7 octaves. }
  160.   Frequency       : array[0..83] of word;
  161.  
  162.  
  163.  
  164.  
  165. function GetNumber(min, max, default : word) : word;
  166. { Get a number from the MusicString, starting at MusicHere. Increment MusicHere
  167.   past the end of the number. If the number is <min or >max then the default
  168.   number is returned. This routine will also skip the Basic syntax for a
  169.   variable: '=name;' }
  170. var
  171.   n : word;
  172. begin
  173. { Ignore Basic syntax for embedded variable instead of constant, and exit with
  174.   the default. }
  175. if (MusicHere <= length(MusicString)) and (MusicString[MusicHere] = '=') then
  176.   begin
  177.   while (MusicHere <= length(MusicString)) and (MusicString[MusicHere] <> ';')
  178.     do inc(MusicHere);
  179.   if (MusicHere <= length(MusicString)) and (MusicString[MusicHere] = ';')
  180.     then inc(MusicHere);
  181.   GetNumber := default;
  182.   exit;
  183.   end;
  184.  
  185. { Accept a number from the MusicString. The number is finished by anything that
  186.   is not a number '0'..'9'. }
  187. n := 0;
  188. while (MusicHere <= length(MusicString)) and
  189.   (MusicString[MusicHere] in ['0'..'9']) do
  190.   begin
  191.   n := n * 10 + (Ord(MusicString[MusicHere]) - Ord('0'));
  192.   inc(MusicHere);
  193.   end;
  194.  
  195. { Test if the number is within range, otherwise return the default. }
  196. if (n < min) or (n > max)
  197.   then GetNumber := default
  198.   else GetNumber := n;
  199. end;
  200.  
  201.  
  202.  
  203.  
  204. procedure SetupDelays;
  205. { Setup MusicDelay1 and MusicDelay2. The first determines the time that a note
  206.   is audible, the second determines a rest between two notes (Legato, Normal,
  207.   Staccato). To do this, accept a note-length number from the MusicString, or
  208.   use the default NoteLength. Also accept trailing dot's from the MusicString,
  209.   which lengthen the note-length by 1.5. }
  210. var
  211.   r : real;
  212. begin
  213. r := GetNumber(1,999,MusicNoteLength);                         { Accept number. }
  214. { Note: the number is reciprocal. A high number means a short note. If the
  215.   number is 4, then it is a 'normal' note. Think of the number as: "the number
  216.   of quarter notes that the note will last". }
  217.  
  218. while (MusicHere <= length(MusicString)) and          { Accept trailing dot's. }
  219.    (MusicString[MusicHere] = '.') do
  220.   begin
  221.   inc(MusicHere);
  222.   r := r * 0.75;             { Every dot increases the note time by 1.5 times. }
  223.   end;
  224.  
  225. { Translate into clocktick delays. The following formula is used:
  226.   There are 120 'standard' notes per minute.
  227.         ticks = ThisNoteLength * ThisTempo * TicksPerStandardNote
  228.         ThisNoteLength = 4 / NoteLength
  229.         ThisTempo = 120 / MusicTempo
  230.         TicksPerStandardNote = TicksPerMinute / 120
  231.         TicksPerMinute = TicksPerSecond * 60
  232.         TicksPerSecond = 18.2
  233.   ticks := 4 * 18.2 * 60 * / (NoteLength * MusicTempo)
  234.   }
  235. MusicDelay1 := Round(1.0 / (R*MusicTempo));
  236.                      { 4368 }
  237. { The clockticks are split two ways: every note is followed by a small amount
  238.   of silence (Legato, Normal, Staccato). MusicDelay1 determines the 'on' time,
  239.   MusicDelay2 determines the 'off' time. }
  240. if MusicKind < 8
  241.   then MusicDelay2 := MusicDelay1 * (8115 - MusicKind) div 8
  242.   else MusicDelay2 := 0;
  243. dec(MusicDelay1,MusicDelay2);
  244. end;
  245.  
  246.  
  247.  
  248.  
  249. procedure MusicNext; interrupt;
  250. { Play the MusicString. This procedure is installed into the timer interrupt,
  251.   and therefore runs with every timer-tick. The routine takes music from the
  252.   MusicString, from position MusicHere. If MusicHere is zero, then the music is
  253.   disabled. The duration of a note is determined by MusicDelay1 and
  254.   MusicDelay2, both set by the SetupDelays procedure. }
  255. var
  256.   note : word;                                          { Temporary variables. }
  257.   ch : char;
  258. begin
  259. { Call the old timer handler. The address of the old handler is saved by the
  260.   installation code at the end of the unit. }
  261. Inline(
  262.   $9C/                   {pushf}
  263.   $FF/$1E/>OLDINT1C);    {call far [>OldInt1C]}
  264.  
  265. { Decrement MusicDelay1. This determines the time that a note is 'on'. }
  266. if MusicDelay1 > 0 then
  267.   begin
  268.   dec(MusicDelay1);                                         { Decrement delay. }
  269.   if MusicDelay1 > 0 then exit;                      { Exit if delay not zero. }
  270.   end;
  271.  
  272. { If there is a second delay, then move it to the main delay counter and exit.
  273.   The second delay time determines a silence after each note (Legato, Normal,
  274.   Staccato). }
  275. if MusicDelay2 > 0 then
  276.   begin
  277.   MusicDelay1 := MusicDelay2;            { Move second delay into first delay. }
  278.   MusicDelay2 := 0;
  279.   Port[$61] := Port[$61] and $F8;                                 { Sound off. }
  280.   exit;                                                                { Exit. }
  281.   end;
  282.  
  283. { If MusicString all done then sound off and exit. }
  284. if MusicHere = 0 then exit;
  285. if MusicHere > length(MusicString) then
  286.   begin
  287.   MusicHere := 0;
  288.   Port[$61] := Port[$61] and $F8;                                 { Sound off. }
  289.   exit;                                                                { Exit. }
  290.   end;
  291.  
  292. { Process commands from MusicString, until a note or a pause can be played. A
  293.   few Basic commands are not supported, these are ignored. }
  294. while MusicHere <= length(MusicString) do
  295.   begin
  296.   ch := upcase(MusicString[MusicHere]);      { Get character from MusicString. }
  297.   inc(MusicHere);
  298.   case ch of
  299.     'O' : MusicOctave := GetNumber(0,7,4);                       { Set octave. }
  300.     'L' : MusicNoteLength := GetNumber(1,955,4);             { Set note length. }
  301.     'T' : MusicTempo := Getnumber(32,955,120);                    { Set tempo. }
  302.     'M' : if MusicHere <= length(MusicString) then             { 'M' commands. }
  303.           begin
  304.           ch := upcase(MusicString[MusicHere]);
  305.           inc(MusicHere);
  306.           case ch of
  307.             'F' : MusicKind := 1;
  308.             'L' : MusicKind := 8;                                { Set legato. }
  309.             'N' : MusicKind := 7;                                { Set normal. }
  310.             'S' : MusicKind := 6;                              { Set staccato. }
  311.             end;
  312.           end;
  313.     'P' : begin                                                       { Pause. }
  314.           Port[$61] := Port[$61] and $F8;
  315.           SetupDelays;
  316.           exit;
  317.           end;
  318.     'A'..'G','>','<' : begin                                    { Play a note. }
  319.           note := MusicOctave * 12;
  320.           if ch = '>' then
  321.             begin                                                { Accept '>'. }
  322.             if MusicHere <= length(MusicString) then
  323.               ch := upcase(MusicString[MusicHere]);
  324.             inc(MusicHere);
  325.             if note <= 71 then inc(note,12);
  326.             end;
  327.           if ch = '<' then
  328.             begin                                                { Accept '<'. }
  329.             if MusicHere <= length(MusicString) then
  330.               ch := upcase(MusicString[MusicHere]);
  331.             inc(MusicHere);
  332.             if note >= 12 then dec(note,12);
  333.             end;
  334.           case ch of                            { Determine frequency of note. }
  335.             'A' : inc(note,9);
  336.             'B' : inc(note,11);
  337.             'C' : inc(note,0);
  338.             'D' : inc(note,2);
  339.             'E' : inc(note,4);
  340.             'F' : inc(note,5);
  341.             'G' : inc(note,7);
  342.             end;
  343.           { Accept '#' or '+' following the letter. }
  344.           if (MusicHere <= length(MusicString)) and
  345.              ( (MusicString[MusicHere] = '#') or (MusicString[MusicHere] = '+') )
  346.              then
  347.             begin
  348.             inc(MusicHere);
  349.             if note < 83 then inc(note);
  350.             end;
  351.           { Accept '-' following the letter. }
  352.           if (MusicHere <= length(MusicString)) and
  353.              (MusicString[MusicHere] = '-') then
  354.             begin
  355.             inc(MusicHere);
  356.             if note > 0 then dec(note);
  357.             end;
  358.           note := Frequency[note];          { Translate note into 'frequency'. }
  359.           Port[$61] := Port[$61] and $F8;                         { Sound off. }
  360.           Port[$43] := $B6;                                { Setup timer chip. }
  361.           Port[$42] := Lo(note);                            { Setup frequency. }
  362.           Port[$42] := Hi(note);
  363.           Port[$61] := Port[$61] or $03;                           { Sound on. }
  364.           SetupDelays;                             { Setup note length delays. }
  365.           exit;
  366.           end;
  367.     'N' : begin                                        { Play a specific note. }
  368.           note := GetNumber(1,84,0);                     { Accept note number. }
  369.           Port[$61] := Port[$61] and $F8;                         { Sound off. }
  370.           if note > 0 then                               { Zero means silence. }
  371.             begin
  372.             note := Frequency[note-1];      { Translate note into 'frequency'. }
  373.             Port[$43] := $B6;                              { Setup timer chip. }
  374.             Port[$42] := Lo(note);                          { Setup frequency. }
  375.             Port[$42] := Hi(note);
  376.             Port[$61] := Port[$61] or $03;                         { Sound on. }
  377.             end;
  378.           SetupDelays;                             { Setup note length delays. }
  379.           exit;
  380.           end;
  381.     'X' : begin                { Skip the Basic syntax for an embedded string. }
  382.           while (MusicHere <= length(MusicString)) and
  383.             (MusicString[MusicHere] <> ';') do inc(MusicHere);
  384.           if (MusicHere <= length(MusicString)) and
  385.             (MusicString[MusicHere] = ';') then inc(MusicHere);
  386.           end;
  387.     end;
  388.   end;
  389. end;
  390.  
  391.  
  392.  
  393.  
  394. procedure MusicOff;
  395. { Turn music off. }
  396. begin
  397. MusicHere := 0;                                               { Index is zero. }
  398. MusicDelay1 := 0;                                             { Delay is zero. }
  399. MusicDelay2 := 0;                                             { Delay is zero. }
  400. Port[$61] := Port[$61] and $F8;                                   { Sound off. }
  401. end;
  402.  
  403.  
  404.  
  405.  
  406. procedure PlayMusic(s : string);
  407. { Start playing a string of music in the background. If there is already music
  408.   playing, then first shut it off. All settings revert to their default. }
  409. begin
  410. MusicOff;                                              { Shutup current music. }
  411. MusicString := s;                              { Save string into MusicString. }
  412. MusicNoteLength := 1; {4}                                        { Setup defaults. }
  413. MusicTempo := 19000; {120;}
  414. MusicOctave := 4;
  415. MusicKind := 1; {7;}
  416. MusicHere := 1;                            { Start music (at begin of string). }
  417. end;
  418.  
  419.  
  420.  
  421.  
  422. procedure PlayMusicForeground(s : string);
  423. { Start playing a string of music, and wait for it to finish. If there is
  424.   already music playing, then first shut it off. All settings revert to their
  425.   default. }
  426. begin
  427. PlayMusic(s);
  428. while MusicHere > 0 do ;
  429. end;
  430.  
  431.  
  432.  
  433.  
  434. function MusicBusy : boolean;
  435. { If there is music playing then return TRUE. }
  436. begin
  437. if MusicHere > 0
  438.   then MusicBusy := true
  439.   else MusicBusy := false;
  440. end;
  441.  
  442.  
  443.  
  444.  
  445. {$F+}                                                  { Must be compiled FAR. }
  446. procedure ShutDown;
  447. { Un-install the unit, and turn music off. It is absolutely necessary that the
  448.   MusicNext procedure is un-installed from the timertick interrupt, or the
  449.   system may crash. }
  450. begin
  451. MusicOff;                                                         { Music off. }
  452. ExitProc := ExitSave;                          { Reinstall old exit procedure. }
  453. SetIntVec($1C,OldInt1C);                      { Install old interrupt handler. }
  454. end;
  455. {$F-}
  456.  
  457.  
  458.  
  459.  
  460. procedure Initialize;
  461. var
  462.   i : word;
  463.   r1, r2 : real;
  464. begin
  465. { Fill the frequency array with words that can be fed into the timer chip. The
  466.   array contains coded frequencies, one for every note (0..11) in every octave
  467.   (0..6). The first note of an octave is exactly 2 times as high as the first
  468.   note in the first-lower octave. This means that the distance between two
  469.   notes is exactly 12√2 = exp(ln(2)/12). Starting at a 'base' frequency for the
  470.   highest note in the highest octave, we can calculate all the notes in all the
  471.   octaves. The timer chip expects a reciprocal number (1193180 / frequency). }
  472. r1 := 1193180.0 / 8000.0;                           { Highest note is 8000 Hz. }
  473. r2 := exp(ln(2.0)/12.0);                           { Distance between 2 notes. }
  474. for i := 83 downto 0 do                                { Fill frequency array. }
  475.   begin
  476.   Frequency[i] := round(r1);
  477.   r1 := r1 * r2;
  478.   end;
  479.  
  480. MusicOff;                                              { Initialize variables. }
  481. GetIntVec($1C,OldInt1C);            { Save address of previous int-1C handler. }
  482. SetIntVec($1C,@MusicNext);                    { Install our interrupt handler. }
  483. ExitSave := ExitProc;               { Save address of previous exit procedure. }
  484. ExitProc := @ShutDown;                           { Install ShutDown procedure. }
  485. end;
  486.  
  487.  
  488.  
  489.  
  490. { Initialization code. }
  491. begin
  492. Initialize;
  493. end.
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500. (***************************** Example program *********************************
  501. program test;
  502. uses music;
  503.  
  504.  
  505. begin
  506. { Anthem }
  507. PlayMusic('T100O3L8E-.L16CO2L4A-O3L4CE-L2A-O4L8C.O3L16B-L4A-CDL2E-L8E-E-O4L4C.'+
  508.   'O3L8B-L4A-L2GL8FGL4A-A-E-CO2L4A-O4L8CCL4CD-E-L2E-L8D-CO3L4B-O4L4CD-L2D-L8D-'+
  509.   'D-L4C.O3L8B-L4A-L2GL8FL16G.L4A-CDL2E-L8E-E-L4A-A-L8A-GL4FFFB-O4L8D-CO3L8B-'+
  510.   'A-L4A-L4G.P8L8E-E-O3L4A-.L8B-O4L8CD-L2E-O3L8A-B-O4L4C.L8D-O3L4B-L2A-..');
  511. while MusicBusy do write('Playing the Anthem....');
  512.  
  513. { Anvil }
  514. PlayMusic('T200O3E2E4.E8E4.D8C4.O2A8G4.B8O3D4.F8E2C2E2E4.E8E4.D8C4.O2A8G4.B8'+
  515.   'O3D4.F8E4C4E2C4P4D4P4O2B4O3C4O2A4B4E4P4P8G+8A8B8O3C4C4P8O2B8O3C8D8E4E4P8D8'+
  516.   'E8F8G2.F8G16F16E4P4P2');
  517. while MusicBusy do write('Playing Anvil....');
  518.  
  519. { Bouree }
  520. PlayMusic('MBMLL8T150O4DEF4EDC+4DEO3A4BO4C+DP10CO3B-A4GFE4FGAP16GF16E16D8P10'+
  521.   'O4DEF4EDA4FAO3A4BO4C+DP10CO3B-A4GFP32F16G16F16E16F16.P32F2');
  522. while MusicBusy do write('Playing Bouree....');
  523.  
  524. writeln('Music is done.');
  525. end.
  526. *******************************************************************************)
  527.